/**
 * 
 */
package fr.utbm.gi.vi51.slizers.pw;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;

import javax.vecmath.Point2i;

import org.janusproject.kernel.agent.Kernel;
import org.janusproject.kernel.agent.KernelAdapter;
import org.janusproject.kernel.agent.KernelEvent;
import org.janusproject.kernel.agent.Kernels;

import fr.utbm.gi.vi51.slizers.framework.agent.SituatedAgent;
import fr.utbm.gi.vi51.slizers.framework.learning.QValueStore;
import fr.utbm.gi.vi51.slizers.framework.schedule.SimulationScheduler;
import fr.utbm.gi.vi51.slizers.pw.agent.Creature;
import fr.utbm.gi.vi51.slizers.pw.environment.World;
import fr.utbm.gi.vi51.slizers.pw.environment.WorldElement;
import fr.utbm.gi.vi51.slizers.pw.gui.ApplicationGUI;
import fr.utbm.gi.vi51.slizers.pw.learning.CreatureQAction;
import fr.utbm.gi.vi51.slizers.pw.learning.CreatureQState;

/**
 * Implementation of a launcher for the world simulation.
 *
 * @author Tarik Belabbas
 *
 */
/**
 * 
 *
 * @author Tarik Belabbas
 *
 */
/**
 * 
 *
 * @author Tarik Belabbas
 *
 */
public class WorldManager
{
	/**
	 * The world of the simulation.
	 */
	private static World			worldSingleton	= null;
	
	/**
	 * The world simulation scheduler.
	 */
	private static SimulationScheduler	scheduler = null;

	/**
	 * The GUI associated to the world
	 */
	private static ApplicationGUI	guiSingleton	= null;

	/**
	 * The execution delay of the simulation.
	 */
	private static long				EXECUTION_DELAY	= 50L;

	/**
	 * The step duration of the simulation.
	 */
	private static float			STEP_DURATION	= 500.0f;
	
	/**
	 * Boolean indicating whether the simulation was launched or not.
	 */
	private static boolean			isSimulationRunning = false;

	/**
	 * Boolean indicating whether the simulation is playing or not
	 */
	private static boolean			isSimulationPlaying	= false;

	/**
	 * The list of creatures learners.
	 */
	private static ArrayList< QValueStore< CreatureQState, CreatureQAction > >	qValueStores	=
		new ArrayList< QValueStore< CreatureQState, CreatureQAction > >();

	/**
	 * Launches the world and its associated UI.
	 * 
	 * @param _gui the gui associated to the world
	 * @param _waitDuration the wait duration at the end of each simulation step
	 *            (in milliseconds)
	 */
	public static void launchEnvironment(ApplicationGUI _gui )
	{
		// Create GUI and environment
		World world = new World( STEP_DURATION );

		if ( worldSingleton == null )
		{
			worldSingleton = world;
			
			guiSingleton = _gui;
			if ( _gui != null )
			{
				world.addEnvironmentListener( _gui );
			}
			
			// Create the kernel of the simulation
			scheduler = new SimulationScheduler( world,	EXECUTION_DELAY );
			Kernel kernel = Kernels.create( scheduler );
			kernel.addKernelListener( new KernelAdapter()
			{
				/**
				 * {@inheritDoc}
				 */
				@SuppressWarnings( "synthetic-access" )
				@Override
				public void agentKilled( KernelEvent _event )
				{
					if ( worldSingleton != null )
					{
						worldSingleton.killAgentBody( _event.getAgent() );
					}
				}
			} );
		}
	}
	
	/**
	 * Creates a creature and launches it in the world.
	 */
	public static void createCreature()
	{
		Creature newCreature = new Creature(worldSingleton.getMapExit());
		
		launchAgent( newCreature );
		qValueStores.add( newCreature.getQValueStore() );
	}
	
	/**
	 * Launches the given agent in the world.
	 * 
	 * @param _agent the agent to launch
	 */
	private static void launchAgent( SituatedAgent< ? > _agent )
	{
		if ( worldSingleton == null )
			throw new IllegalStateException();

		Kernel k = Kernels.get();
		if ( k == null )
			throw new IllegalStateException();

		worldSingleton.spawnAgentBody( _agent );

		// Submit the given agent to be launched as a light agent at the next
		// invocation of {@link org.janusproject.kernel.agent.Kernel#launchDifferedExecutionAgents()}.
		k.submitLightAgent( _agent );
	}

	/**
	 * Starts the simulation immediately, launches agents, displays GUI.
	 */
	public static void startSimulation()
	{
		// Launch all the agents which has been previously submitted as
		// differed execution agents
		Kernel kernel = Kernels.get();
		if ( kernel == null )
		{
			throw new IllegalStateException();
		}
		kernel.launchDifferedExecutionAgents();
		
		// Display the GUI
		if ( guiSingleton != null )
		{
			guiSingleton.setVisible( true );
		}
		
		isSimulationRunning = true;
		isSimulationPlaying = true;
	}

	/**
	 * Stops the simulation immediately, kills agents, exits application.
	 */
	public static void stopSimulation()
	{
		Kernels.killAll();
		
		isSimulationRunning = false;
	}
	
	/**
	 * Pauses the simulation.
	 */
	public static void pauseSimulation()
	{
		scheduler.pause();
		
		isSimulationPlaying = false;
	}
	
	/**
	 * Resumes the simulation.
	 */
	public static void resumeSimulation()
	{
		scheduler.resume();
		
		isSimulationPlaying = true;
	}
	
	/**
	 * Returns <tt>true</tt> if the simulation is running.
	 * 
	 * @return <tt>true</tt> if the simulation is running
	 */
	public static boolean isSimulationRunning()
	{
		return isSimulationRunning;
	}
	
	/**
	 * Returns <tt>true</tt> if the simulation is playing.
	 * 
	 * @return <tt>true</tt> if the simulation is playing
	 */
	public static boolean isSimulationPlaying()
	{
		return isSimulationPlaying;
	}
	
	/**
	 * Loads a map and sets it as the world map.
	 * 
	 * @param _mapPath the file map path
	 */
	public static void loadMap( String _mapPath )
	{
		File mapFile = new File( _mapPath );
		Point2i mapEntrance = null;
		Point2i mapExit = null;
		List< List< WorldElement > > map = null;
		Scanner inFlux = null;
		int cellSize = 0;
		try
		{
			FileInputStream stream = new FileInputStream( mapFile.getAbsolutePath() );
			inFlux = new Scanner( stream );
			
			// Read the map height
			String line = inFlux.nextLine();
			int worldHeight = Integer.parseInt( line );
			
			// Read the map width
			line = inFlux.nextLine();
			int worldWidth = Integer.parseInt( line );
			
			// Read the cellSize
			line = inFlux.nextLine();
			cellSize = Integer.parseInt( line );
			if ( cellSize % 10 != 0)
			{
				cellSize = 10;
			}

			StringTokenizer st = null;

			WorldElement[][] array = new WorldElement[ worldWidth ][ worldHeight ];

			for ( int i = 0; i < worldHeight; i++ )
			{
				line = inFlux.nextLine();
				st = new StringTokenizer( line );
				
				for ( int j = 0; j < worldWidth; j++ )
				{
					WorldElement currentElement = WorldElement.valueOf( st.nextToken() );
					if ( currentElement == WorldElement.ENTRANCE )
					{
						mapEntrance = new Point2i( j, i );
					}
					else if ( currentElement == WorldElement.EXIT )
					{
						mapExit = new Point2i( j, i );
					}
					
					array[ j ][ i ] = currentElement;
				}
			}
			
			map = new ArrayList< List< WorldElement > >( worldWidth );
			for ( int i = 0; i < worldWidth; ++i )
			{
				List< WorldElement > mapColumn = new ArrayList< WorldElement >( worldHeight );
				for ( int j = 0; j < worldHeight; ++j )
				{
					mapColumn.add( array[ i ][ j ] );
				}
				
				map.add( mapColumn );
			}
		}
		catch ( FileNotFoundException e )
		{
			System.out.println( "!File not found!" ); //$NON-NLS-1$
		}
		
		assert( cellSize >= 0 );
		
		worldSingleton.setMap( map, mapEntrance, mapExit );
		guiSingleton.setCellSize( cellSize );
	}
	
	/**
	 * Saves the creatures learning.
	 * 
	 * @param _fileName
	 */
	public static void saveCreatures( String _fileName )
	{
		File filePath = new File( System.getProperty( "user.dir" ).concat( "/creatures/" ) + _fileName );
		FileOutputStream out = null;
		ObjectOutputStream objectOut = null;
		try
		{
			out = new FileOutputStream( filePath.getAbsolutePath() );
			objectOut = new ObjectOutputStream( out );
			objectOut.writeObject( qValueStores );
		}
		catch ( IOException e )
		{
			try
			{
				out.close();
				objectOut.close();
			}
			catch ( IOException e1 )
			{
				e1.printStackTrace();
			}
		}		
	
		try
		{
			out.close();
			objectOut.close();
		}
		catch ( IOException e1 )
		{
			e1.printStackTrace();
		}
	}

	public static void loadCreatures( String _creaturesPath )
	{
		File filePath = new File( _creaturesPath );
		FileInputStream in = null;
		ObjectInputStream objectIn = null;
		try
		{
			in = new FileInputStream( filePath.getAbsolutePath() );
			objectIn = new ObjectInputStream( in );
			Object readObject = objectIn.readObject();
			
			qValueStores = ( ArrayList< QValueStore< CreatureQState, CreatureQAction >> ) readObject;
			
			for ( QValueStore< CreatureQState, CreatureQAction > store : qValueStores )
			{
				Creature newCreature = new Creature( store, worldSingleton.getMapExit() );
				
				launchAgent( newCreature );
			}
		}
		catch ( IOException e )
		{
			try
			{
				in.close();
				objectIn.close();
			}
			catch ( IOException e1 )
			{
				e1.printStackTrace();
			}
		}
		catch ( ClassNotFoundException e )
		{
			e.printStackTrace();
		}

		try
		{
			in.close();
			objectIn.close();
		}
		catch ( IOException e1 )
		{
			e1.printStackTrace();
		}
	}
}
